- Published on
了解 JsonWebToken
JWT(json web token)是一种开源的(RFC7519),工业级别的,用于客户端服务端鉴权的协议机制。
通常有三部分组成 header
,payload
, signature
。
header
{
"alg": "HS256",
"typ": "JWT"
}
alg 指明摘要算法的类型, 基本上我们可以使用任何摘要算法,如HMAC SHA256, SHA512,MD5 等。
payload payload分为三个部分
- 标准中注册的声明
- 公共的声明
- 私有的声明
标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的. iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
signature signature 的部分是将 header 和 payload 分别用 base64 编码之后,用 .
连接起来,再使用 header 中指定的摘要算法进行计算(需要 secret ),最后得到 jwt 字符串。
// javascript
let encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload)
let signature = HMACSHA256(encodedString, 'secret') // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
let jwttoken = encodeString + '.' + signature
工作流程一般是:
- 用户使用账号密码登录
- 服务器根据登录的用户生成一个jwttoken,在 payload 部分可以携带部分用户信息,如用户id
- 客户端之后每次发送请求的时候都携带这个 jwttoken 以供服务器鉴权
思考
- 传统的 session token 需要在服务器上面缓存,而且一般是在内存中,这不利于扩展,后期对服务器的压力不可忽略。并且无法解决分布式站点单点登录 SSO 的问题。而 JWT 方式不需要服务器缓存,它只需要在请求中校验。
- 基于 cookie 的 session 验证容易被利用来进行 CSRF 攻击(跨站伪造请求)。
- 在 JWT 的 payload 部分不应该放敏感信息,因为这部分只是经过 base64 编码而已,相当于明文。
- secret 至关重要,不应泄露,否则别人会伪造token。
- JWT 最好是配合 https 使用,防止请求被拦截。
单点登录
JWT token 的生成和验证可以不在同一个服务中。 验证的时候只需要将 header 和 payload 部分连接起来,用 header 中指定的 alg
算法进行摘要生成 signature。 然后比较这个 signature 和 token 中的 signature 是否一致,就可以判断是否有效。
这个天然的去中心化很适合用来实现单点登录。
单点登录英文全称Single Sign On,简称就是SSO。它的解释是: 在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。
- 用户在系统 A登录,获得 jwt token。
- 用户使用 A 系统的 token 去访问 B 系统的资源,由于 A,B 系统使用相同的 JWT 密钥,那么 A 系统下发的 token, 在 B 系统也是有效的。
撤销 JWT token ?
jwt token 在 exp
过期时间前都是有效的。JWT token 一旦下发,服务端就不能主动撤销该 token 的有效性。 也就是说服务端不能对已经下发的 token 修改过期时间,相当于将 session 状态交由了客户端管理,服务端无法拒绝该 JWT 的请求(无法踢除用户)
有两种方案可以缓解这种无法拒绝服务的问题:
- 黑名单 jwt
- 和 refreshToken 配合的新交换方式。
🤣 这两种方式都需要服务端多维护一个 blacklist 或者是 refresh token, 所以为什么和传统的方案,将 session 存储于服务端没啥区别,😝甚至是更复杂啦。。。。。